There are four methods of providing info to the user or developer. Of these, aliases and log files are suitable for end users. Callbacks and Apple Events are intended for developers.
Creating an alias is a little involved. I like the example in [Little91]. Below are a few code snippets that handle potential problems.
Remember to put the alias file somewhere meaningful. You may want to create your own folder to hold the aliases. The following code checks if a named folder exists, and if not, creates it.
First, find the parent folder of our intended destination folder. Here, the parent is Apple Menu Items, so our folder should appear in the Apple Menu.
long theFolderId; CInfoPBPtr thePBPtr; FSSpec theFSSpec; OSErr theError; theError = FindFolder( kOnSystemDisk, kAppleMenuFolderType, kCreateFolder, &theFSSpec.vRefNum, &theFSSpec.parID );
Create the new directory. If it already exists, the call will fail, and we need to get the existing dir id using PBGetCatInfo().
theError = DirCreate( theFSSpec.vRefNum, theFSSpec.parID, “\pMy Folder”, &theFolderId ); if ( theError != noErr ) { theError = DoFindOneFolder( fileError = FindFolder( kOnSystemDisk, kAppleMenuFolderType, kCreateFolder, &theFSSpec.vRefNum, &theFSSpec.parID ); thePBPtr->dirInfo.ioCompletion = 0L; thePBPtr->dirInfo.ioNamePtr = “\pMy Folder”; thePBPtr->dirInfo.ioVRefNum = theFSSpec.vRefNum; thePBPtr->dirInfo.ioDrDirID = theFSSpec.parID; thePBPtr->dirInfo.ioFDirIndex = 0; theError = PBGetCatInfo( thePBPtr, true ); while ( thePBPtr->dirInfo.ioResult == 1 ) { ; }
Check that the call succeeded, and returned a valid directory id.
if ( ( thePBPtr->dirInfo.ioResult == noErr ) && ( theError == noErr ) && ( thePBPtr->dirInfo.ioFlAttrib & 0x0010 ) ) theFolderID = thePBPtr->dirInfo.ioDrDirID; }Listing 5. Locating the destination directory.
Now use that directory id when creating the alias file. The last line of code in this listing establishes our special folder as the destination for the alias.
FSSpecPtr aliasSpec; OSErr fileError; BlockMove( theFSSpec.name, aliasSpec.name, theFSSpec.name[ 0 ] + 1 ); fileError = FindFolder( kOnSystemDisk, kAppleMenuFolderType, kDontCreateFolder, &aliasSpec.vRefNum, &aliasSpec.parID ); aliasSpec.parID = theFolderID; // Now call FSpCreateResFile(), etc.Listing 6. Settng up to create the new alias.
Logging data to a file is easy, as long as you’re appending. This next example appends to a text file. The record format looks like this:
DateTime Path to file
For example:
Wed, May 12, 1999 10:47:05 PM Devt::tmp data
Since the user may delete the log file at any time, ensure it exists before opening.
FSSpecPtr logFSSpecPtr; OSErr fileError; fileError = HCreate( ( *logFSSpecPtr ).vRefNum, ( *logFSSpecPtr ).parID, ( *logFSSpecPtr ).name, kLogFileCreatorType, kLogFileType );
Open, and move to the end of, the file.
short fileRef; fileError = FSpOpenDF( logFSSpecPtr, kSharedPermission, &fileRef ); SetFPos( fileRef, fsFromLEOF, 0 );
Using the current time as a timestamp avoids the setup associated with (yet another call to) PBGetCatInfo(). But it’s less accurate. As long as we get called soon enough, the difference may only be a few seconds.
unsigned long theTime; GetDateTime( &theTime );
Convert the timestamp into a human-readable date. Then write it out.
Str32 theString, IUDateString( theTime, abbrevDate, theString ); dataCount = theString[ 0 ];
Move the date string into a buffer, then write the buffer to the file. Remember to initialize any pointers (not shown).
long dataCount; short i; Ptr dataPtr; // Faster than BlockMove()? for ( i = 0; i < dataCount; i++ ) dataPtr[ i ] = theString[ i + 1 ]; fileError = FSWrite( fileRef, &dataCount, dataPtr );
Adding a
*dataPtr = '\t'; dataCount = 1; fileError = FSWrite( fileRef, &dataCount, dataPtr );
Adding the time string is done the same way, but is not shown.
Next, get the volume name.
ParamBlockRec theVolume; OSErr fileError; theVolume.volumeParam.ioCompletion = 0L; theVolume.volumeParam.ioNamePtr = ( StringPtr )&theString; theVolume.volumeParam.ioVRefNum = theVRefNum; theVolume.volumeParam.ioVolIndex = 0; fileError = PBGetVInfo( &theVolume, true );
Writing the volume name to the file is done the same way as the date string. Don’t forget to add the two colons after the name.
Next, iterate over the directories in the file’s path.
CInfoPBPtr thePBPtr; fileError = noErr; while ( fileError == noErr ) { thePBPtr->dirInfo.ioCompletion = 0L; thePBPtr->dirInfo.ioNamePtr = tempStringPtr; thePBPtr->dirInfo.ioVRefNum = theVRefNum; thePBPtr->dirInfo.ioDrDirID = tempFolderID; thePBPtr->dirInfo.ioFDirIndex = -1; fileError = PBGetCatInfo( thePBPtr, true ); while ( thePBPtr->dirInfo.ioResult == 1 ) { ; }
As long as the result refers to a directory, use the id to find the parent of this directory.
if ( ( thePBPtr->dirInfo.ioResult == noErr ) && ( fileError == noErr ) && ( thePBPtr->dirInfo.ioFlAttrib & 0x0010 ) ) thePBPtr->dirInfo.ioDrDirID = thePBPtr->dirInfo.ioDrParID; }
Each iteration will place the name of the directory in the dirInfo.ioNamePtr field. This is the value we want to print, followed by a colon. However, since we’re iterating up the directory tree, we should store the names temporarily, then write them out in the correct sequence after reaching the root.
Finally, write the name of the file, followed by a
StringPtr fileNamePtr; dataCount = fileNamePtr[ 0 ]; for ( i = 0; i < dataCount; i++ ) dataPtr[ i ] = fileNamePtr[ i + 1 ]; dataPtr[ i ] = '\r'; dataCount++; fileError = FSWrite( fileRef, &dataCount, dataPtr );
Dispose of any pointers, close the file, and flush the volume.
DisposePtr( dataPtr ); FSClose( fileRef ); fileError = FlushVol( NIL, ( *logFSSpecPtr ).vRefNum );Listing 7. Logging new file info.
Now let’s look at notification from a developer’s perspective.
Callbacks are great from the perspective of time. But, they can be difficult to setup initially. And you need to make sure you don’t call a routine that has moved! This will certainly cause problems if the application you’re calling has quit.
Registering a callback requires that both sides know what to expect. For example, we can use one registration mechanism if we can distinguish between the types of activities that should be sent to a particular callback.
enum { kCreateFlag, kDeleteFlag, kCopyFlag, kRenameFlag };
Similarly, the structure of the data being exchanged must be defined. Here, we’ve added a few fields to those defined by an FSSpec. This helps the caller in several ways:
The caller still has access to the various File Manager calls to get more information if necessary.
typedef struct FWData { short theTrapId; short theVRefNum; long theParID; Str63 theString; OSType theFileType; } FWData, *FWDataPtr;
This structure defines the data sent during an actual registration. The function address is sent, and the recipient needs to create a UniversalProcPtr based on the action specified (see the previous enum definition).
typedef struct FWSubscribe { short theAction; long *theCallbackAddr; } FWSubscribe, *FWSubscribePtr;
The callback convention looks as follows:
ProcInfoType uppCreateFileProcInfo = kPascalStackBased | RESULT_SIZE( kNoByteCode ) | STACK_ROUTINE_PARAMETER( 1, SIZE_CODE( sizeof( FWData ) ) ) ;
Sending in a registration request might look something like this:
OSErr theErr = noErr; SelectorFunctionUPP myGestaltUPP; FWSubscribePtr theFWSubscribePtr;
Locate the function handling callback registration. Using Gestalt(), you need to know the advertised signature.
theErr = ::Gestalt( kTargetSignature, ( long * )&myGestaltUPP ); if ( theErr == noErr ) {
Allocate and fill a structure specifying the type of activity we’re interested in, and the address of the callback function.
theFWSubscribePtr = ( FWSubscribePtr ) NewPtr( sizeof( FWSubscribe ) ); if ( theFWSubscribePtr != nil ) { theFWSubscribePtr->theAction = kCreateFlag; theFWSubscribePtr->theCallbackAddr = ( long * )&MyCreateCallback; theErr = CallSelectorFunctionProc( myGestaltUPP, GESTALT_ADD_CALLBACK, ( long * )theFWSubscribePtr ); DisposePtr( ( Ptr )theFWSubscribePtr ); } }Listing 8. Registering a callback: requester.
On the receiving side, the corresponding registration process looks like this in the Gestalt() handler.
theAction = ( ( FWSubscribePtr ) theResponse )->theAction; if ( theAction == kCreateFlag ) gCallbackProcPtr = NewRoutineDescriptor( ( ProcPtr )( ( FWSubscribePtr ) theResponse )->theCallbackAddr, uppCreateFileProcInfo, kPowerPCISA );Listing 9. Registering a callback: receiver.
We distinguish between the activities in case we want to manage _Create callbacks differently. Notice that a UPP is built here. This example also assumes the PowerPC instruction set architecture.
This example uses a single global variable to hold the callback UPP. Using a list or queue would increase the potential number of clients. [Rentzsch99] addresses this issue; you should definitely check it out.
Call the callback via CallUniversalProc(). Don’t forget to populate the data structure first.
FWData theFWData; theFWData.theVRefNum = gParamBlockCopy->fileParam.ioVRefNum; // etc. CallUniversalProc( gCallbackProcPtr, uppCreateFileProcInfo, theFWData );Listing 10. Calling a developer-provided routine with the new file info.
On the receiving side, the stack may need adjusting to account for the first two params to CallUniversalProc(). (This was found to be true when interacting with a shared library.)
Upon entering the callback, increment the stack pointer by two words:
asm void PrologGlue() { addi sp, sp, 0x08 blr }
When leaving the callback, decrement the stack pointer by the same amount:
asm void EpilogGlue() { subi sp, sp, 0x08 blr }
This particular callback is part of a PowerPlant project, and simply relays the incoming data to a separate method in the application:
pascal void MyCreateCallback( FWData theFWData ) { PrologGlue(); ( ( FileWatcherDemo* ) parentApp )->HandleCreate( theFWData ); EpilogGlue(); }Listing 11. Inside the callback.
Figure 3 shows the raw information being fed (via the callback just discussed) to a text area for diagnostic purposes. It displays the FSSpec-related fields associated with a file creation.
Figure 3. Demo app receiving info from the file system watcher.
Apple Events provide another convenient mechanism to transmit data from one component to another. This section discusses the sending of file creation information from the patch to the background app.
In order to support scripting, and also to allow for checking of required data elements, the background app contains an 'aete' resource that defines a custom Apple Event for handling file creation. This example uses an FSSpec, rather than the FWData type discussed in the section on callbacks. The FSSpec is sent as the direct parameter for this event.
resource 'aete' ( 0 ) { 0x1, 0x0, english, roman, { "File Watcher Suite", "Specialized stuff.", 'FWAE', 1, 1, { // Events "handle new file", "A new file to record.", 'asd9', 'newf', noReply, "No reply.", replyOptional, singleItem, notEnumerated, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, '****', "FSSpec for new file", directParamRequired, singleItem, notEnumerated, doesntChangeState, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, reserved, {} }, {}, {}, {}, }, };Listing 12. Resource defining a file create Apple Event.
Preparing and sending an Apple Event is addressed in [Little91]. This code, located in the patch, populates an event with the most recent _Create data:
AEAddressDesc theAddressDesc; AppleEvent theAppleEvent; FSSpec theSpec; OSErr theError; theError = FSMakeFSSpec( gParamBlockCopy->fileParam.ioVRefNum, gParamBlockCopy->fileParam.ioDirID, gParamBlockCopy->fileParam.ioNamePtr, &theSpec ); theError = AECreateAppleEvent( 'asd9', 'newf', &theAddressDesc, kAutoGenerateReturnID, kAnyTransactionID, &theAppleEvent ); theError = AEPutParamPtr( &theAppleEvent, keyDirectObject, typeFSS, &theSpec, sizeof( FSSpec ) );Listing 13. Populating an Apple Event.
The receiver of this Apple Event must be prepared to handle it. Here, the background app registers the function HandleCreate() as the recipient of file creation events. The class and id match those found in the 'aete' resource.
const OSType kTestEventClass ='asd9'; const OSType kCreateEventId = 'newf'; AEInstallEventHandler( kTestEventClass, kCreateEventId, (AEEventHandlerProcPtr)HandleCreate, 0, false ); }
The function HandleCreate() will extract the data from the event, and pass it off for additional processing. For our custom event, the itemsInList value should always be one.
FSSpec theFSS; OSErr theErr; SFTypeList theTypeList; Size actualSize; for ( i = 1; i <= itemsInList; i++ ) { theErr = AEGetNthPtr( &docList, i, typeFSS, &theKeyword, &typeCode, ( Ptr )&theFSS, sizeof( FSSpec ), &actualSize ); theErr = DoMakeAlias( theFSS ); }Listing 14. Handling an Apple Event.